package itecs;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;

import org.cloudbus.cloudsim.CloudletSchedulerSpaceShared;
import org.cloudbus.cloudsim.Datacenter;
import org.cloudbus.cloudsim.DatacenterCharacteristics;
import org.cloudbus.cloudsim.Host;
import org.cloudbus.cloudsim.Pe;
import org.cloudbus.cloudsim.Storage;
import org.cloudbus.cloudsim.UtilizationModelFull;
import org.cloudbus.cloudsim.Vm;
import org.cloudbus.cloudsim.VmAllocationPolicySimple;
import org.cloudbus.cloudsim.VmSchedulerSpaceShared;
import org.cloudbus.cloudsim.core.CloudSim;
import org.cloudbus.cloudsim.distributions.UniformDistr;
import org.cloudbus.cloudsim.provisioners.BwProvisionerSimple;
import org.cloudbus.cloudsim.provisioners.PeProvisionerSimple;
import org.cloudbus.cloudsim.provisioners.RamProvisionerSimple;

import brokers.FCFSBroker;
import brokers.HOLBroker;
import models.ExtendedCloudlet;
import models.SLA;
import util.Globals;
import util.MathUtils;

public class AllocationComparator {

	private String serviceDistribution;
	private SLA sla;
	private double lambda;
	private long seed;
	private int m;
	private boolean SLAsIsMet;
	private ArrayList<Vm> vmList= new ArrayList<Vm>();
	private ArrayList<ExtendedCloudlet> cloudletList = new ArrayList<ExtendedCloudlet> ();
	private FCFSBroker broker;
	private UtilizationModelFull utilizationModel;


	public AllocationComparator(SLA sla, String serviceDistribution, double lambda) throws Exception{

		this.serviceDistribution = serviceDistribution;
		this.sla = sla;
		this.lambda = lambda;

	}


	public void startWithParams(long seed, int numberOfServer) throws Exception{

		this.seed = seed;
		this.m = numberOfServer;
		double mu = 1/Globals.CLOUDLET_LENGTH_MEAN;

		System.out.println("\nINITIALIZING DEDICATED ITECS WITH " + this.m + " AND "+ this.seed +  " as SEEDS");

		vmList.clear();

		cloudletList.clear();

		initTheCloud(m);

		broker = new FCFSBroker("FCFSbroker", Globals.SIMULATION_TIME,  sla, null);


		ArrayList<Double> serviceTimes = null;

		ArrayList<Double> arrivalTimes = MathUtils.getArrivals(this.generateSeed(), lambda);


		if (serviceDistribution.equals("EXP")) 
		{
			serviceTimes = MathUtils.getExponentialServiceTimes(this.generateSeed(), mu, arrivalTimes.size());
		}
		else if (serviceDistribution.equals("PARETO")) 
		{
			serviceTimes = MathUtils.getParetoServiceTimes(this.generateSeed(), Globals.PARETO_SHAPE, Globals.PARETO_LOCATION, arrivalTimes.size());
		}
		else 
		{
			serviceTimes = MathUtils.getExponentialServiceTimes(this.generateSeed(), mu, arrivalTimes.size());
			System.out.println("Default service time distribution: EXPONENTIAL");
		}

		vmList = getVms(broker.getId(), m);

		cloudletList = getCloudlets(arrivalTimes, serviceTimes);

		broker.submitVmList(vmList);
		broker.submitCloudletList(cloudletList);

		CloudSim.startSimulation();
		CloudSim.stopSimulation();

		this.SLAsIsMet = broker.SLAsAreMet();

	}



	public static int getM(int startingM, SLA sla, double lambda, long seed, String serviceDistribution) throws Exception{

		int m = startingM;


		AllocationComparator ac = new AllocationComparator(sla, serviceDistribution, lambda);

		while (ac.isMetable()) {	
			ac.startWithParams(seed, m);
			ac.printStats();
			m++;
		}

		if (ac.SLAsIsMet()){
			return m;
		}
		else {
			return -1;
		}



	}

	public boolean SLAsIsMet() {
		return SLAsIsMet;
	}


	private void printStats() {
		broker.printStatistics();		
	}


	public static int getOptimalM(SLA sla, double lambda, String serviceDistribution) throws Exception {
		int mcorrect = 0;
		int seedIndex = 0;
		int m = Globals.SHARED_STARTING_M;

		while (mcorrect!=3 && seedIndex < Globals.SEEDS.length){
			m = AllocationComparator.getM(m, sla, lambda,Globals.SEEDS[seedIndex], serviceDistribution);

			if(m==-1)
				return -1;

			AllocationComparator ac = new AllocationComparator(sla, serviceDistribution, lambda);
			mcorrect++;
			int nextseedIndex=(seedIndex +1)%Globals.SEEDS.length;
			for (int j=0; j<3; j++){
				ac.startWithParams(Globals.SEEDS[(nextseedIndex+j)%Globals.SEEDS.length], m);
				ac.printStats();
				if (ac.SLAsIsMet()){
					mcorrect++;
					if (mcorrect==3){
						break;
					}
				}
				else {
					mcorrect = 0;
					seedIndex = (nextseedIndex+j)%Globals.SEEDS.length; 
					break;
				}
			}
		}
		System.out.println("Finally m is: " + m);
		return m;
	}


	private boolean isMetable() {
		if (broker==null){
			return true;
		}
		return !SLAsIsMet && broker.getMaxQueueSize()!=0;
	}



	private ArrayList<ExtendedCloudlet> getCloudlets(ArrayList<Double> arrivalTimes1,  ArrayList<Double> serviceTimes1) {

		ArrayList<ExtendedCloudlet> list = new ArrayList<ExtendedCloudlet>();
		ExtendedCloudlet c;
		utilizationModel=new UtilizationModelFull();
		int id = 0;

		for (int i = 0; i < arrivalTimes1.size(); i++){
			c = createCloudlet(id++, arrivalTimes1.get(i), serviceTimes1.get(i).longValue(), 1);
			list.add(c);
		}

		return list;
	}

	private ExtendedCloudlet createCloudlet(int id, Double arrivalTime, long serviceTime, int classId) {

		long cloudletLength =  serviceTime;

		ExtendedCloudlet c = new ExtendedCloudlet(id, cloudletLength, Globals.CPU_PER_CLOUDLET, Globals.CLOUDLET_FILE_SIZE, Globals.CLOUDLET_OUTPUT_SIZE, utilizationModel, utilizationModel, utilizationModel);
		c.setClassType(classId);
		c.setUserId(broker.getId());
		c.setExecStartTime(arrivalTime.doubleValue());
		c.setActualSubmitTime(arrivalTime.doubleValue());

		return c;
	}

	private void initTheCloud(int hostNumber) {
		CloudSim.init(1, Calendar.getInstance(), false);

		// DATACENTER CREATION
		this.createDatacenter(hostNumber);
	}

	public long generateSeed()						
	{
		long a=1220703125; //moltiplicatore a=5^13
		Long m=new Long("2147483648"); //modulo m=2^31
		for(int i=0;i<20;i++)
			seed=(long)((a*seed)%(m.doubleValue()));		
		return seed;
	}

	private ArrayList<Vm> getVms(int brokerId, int vmsNumber) {

		ArrayList<Vm> list = new ArrayList<Vm>(); 

		for (int i=0; i<vmsNumber; i++){
			Vm vm = new Vm(i, brokerId, Globals.VM_MIPS, Globals.VM_CPU_NUMBER, Globals.VM_RAM, Globals.VM_BW, Globals.VM_SIZE, Globals.VM_NAME + "-" + i, new CloudletSchedulerSpaceShared());
			list.add(vm);
		}

		return list;
	}

	/**
	 * Creates the datacenter.
	 *
	 * @param hostNumber the number of desired host
	 * in datacenter
	 *
	 * @return the datacenter
	 */
	private Datacenter createDatacenter(int hostNumber) {

		List<Host> hostList = new ArrayList<Host>();

		int hostId = 0;
		for (int i = 0; i < hostNumber; i++) {
			List<Pe> peList = new ArrayList<Pe>();
			peList.add(new Pe(i, new PeProvisionerSimple(Globals.PE_MIPS)));

			hostList.add(new Host(hostId+i, new RamProvisionerSimple(Globals.HOST_RAM),
					new BwProvisionerSimple(Globals.HOST_BW), Globals.HOST_STORAGE, peList,
					new VmSchedulerSpaceShared(peList))); 
		}

		System.out.println("Host array size: " + hostList.size());

		LinkedList<Storage> storageList = new LinkedList<Storage>(); 

		DatacenterCharacteristics characteristics = new DatacenterCharacteristics(
				Globals.DATACENTER_ARCH, Globals.DATACENTER_OS, Globals.DATACENTER_VMM, hostList, Globals.DATACENTER_TIME_ZONE, 
				Globals.DATACENTER_COST_PER_CPU, Globals.DATACENTER_COST_PER_MEM, Globals.DATACENTER_COST_PER_STORAGE, Globals.DATACENTER_COST_PER_BW);

		Datacenter datacenter = null;
		try {
			datacenter = new Datacenter(Globals.DATACENTER_NAME, characteristics, new VmAllocationPolicySimple(hostList), storageList, 0);
			System.out.println(Globals.DATACENTER_NAME + " has id: " + datacenter.getId());
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("error in creating datacenter: aborting");
			System.exit(-1);
		}

		return datacenter;
	}



	private static double getSharedM(String serviceDistribution, SLA sla1, SLA sla2, int l1, int l2) {

		int a = -1;
		int b = -1;
		try {
			a = getOptimalM(sla1, l1 + l2, serviceDistribution);
			b = getOptimalM(sla2, l1 + l2, serviceDistribution);
		} catch (Exception e) {
			e.printStackTrace();
		}

		return Math.max(a, b);

	}


	private static double getDedicatedM(String serviceDistribution, SLA sla1, SLA sla2, int l1, int l2) {

		int a = -1;
		int b = -1;
		try {
			a = getOptimalM(sla1, l1, serviceDistribution);
			b = getOptimalM(sla2, l2, serviceDistribution);
		} catch (Exception e) {
			e.printStackTrace();
		}

		return a + b;
	}
	
	
	public static void main(String[] args){


		FileWriter file;
		try {
			file = new FileWriter("gslas/comparator.csv", true);
			file.write("serviceDistribution" + "\t" + "sla1" + "\t"  + "sla2" + "\t"  + "l1" + "\t"  + "l2" + "\t"  + "dedicated" + "\t"  + "shared"  + "\t" + "D" + "\n");
			file.flush();
			file.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

		int start = 3;
		int end = 8;
		int endingpoint = 30;
		UniformDistr ud1=new UniformDistr(start, end, Globals.SEEDS[0]);
		UniformDistr ud2=new UniformDistr(start, end, Globals.SEEDS[1]);
		double x1 = ud1.sample();
		double yarray[] = { 0.80, 0.85, 0.90, 0.95 };
		double y1 = yarray[(int) (ud1.sample() % yarray.length)];
		double x2 = ud2.sample();
		double y2 = yarray[(int) (ud2.sample() % yarray.length)];

		SLA slas[][] = new SLA[2][endingpoint];

		for (int i = 0; i < endingpoint; i++) {
			x1 = ud1.sample();
			y1 = yarray[(int) (ud1.sample() % yarray.length)];
			x2 = ud2.sample();
			y2 = yarray[(int) (ud2.sample()%yarray.length)];
			SLA sla1 = new SLA(x1, y1);
			SLA sla2 = new SLA(x2, y2);
			slas[0][i] = sla1;
			slas[1][i] = sla2;
		}

		int ls = 10;
		int le = 40;
		int lj = 10;
		int ll = 40;

		String[] serviceDistributions = {"EXP"};


		for (int j = 0; j < serviceDistributions.length; j++) {

			String serviceDistribution = serviceDistributions[j];

			for(int i = 0; i < 10; i++){
				for (int l1 = ls; l1 < le; l1 += lj) {
					for (int l2 = ls; l2 < le; l2 += lj) {
						if (l1 + l2 > ll)
							continue;

						double D = Math.abs(HOLBroker.getGSLAFromFile(slas[0][i]) - HOLBroker.getGSLAFromFile(slas[1][i]));
						
						System.out.println(slas[0][i] + " " + slas[1][i]);
						double d = getDedicatedM(serviceDistribution, slas[0][i], slas[1][i], l1, l2);

						double s = getSharedM(serviceDistribution, slas[0][i], slas[1][i], l1, l2);

						try {
							file = new FileWriter("gslas/comparator.csv", true);
							file.write(serviceDistribution + "\t" + slas[0][i] + "\t"  +slas[1][i] + "\t"  + l1 + "\t"  + l2 + "\t"  + d + "\t"  + s  + "\t" + D + "\n");
							file.flush();
							file.close();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}
			}
		}
	}

}
